// Copyright (c) 2023 Presto Labs Pte. Ltd.
// Author: sangyul

#pragma once

#include <memory>
#include <vector>
#include <map>
#include <string>
#include <utility>
#include <unordered_map>
#include <unordered_set>

#include "presto/quant/math/moving_average.h"
#include "presto/quant/math/sampled_moving_average.h"

#include "coin/proto/coin_executor.pb.h"
#include "coin/proto/coin_order_enums.pb.h"
#include "coin/proto/coin_telemetry.pb.h"

#include "coin2/exchange/base/log/strategy_logger.h"
#include "coin2/exchange/base/symbology/product_encyclopedia.h"

#include "coin2/strategy/linear_model/logic.h"
#include "coin2/strategy/linear_model/util.h"
#include "coin2/strategy/linear_model/mm_util.h"
#include "coin2/strategy/linear_model/mm_util2.h"
#include "coin2/strategy/linear_model/focus.h"
#include "coin2/strategy/linear_model/focus2.h"
#include "coin2/strategy/linear_model/product_strategy.h"
#include "coin2/strategy/order_executor/executor.h"


namespace coin2::strategy::linear_model {

using ::coin2::exchange::base::symbology::ProductEncyclopedia;
using ::coin2::exchange::base::order::OrderUpdate;
using ::coin2::strategy::order_executor::OrderExecutorSystem;


// represents 1 symbol
class LmFocusLogic3 : public LinearModelLogic {
 public:
  explicit LmFocusLogic3(
      const MarketExchangeApi& mea,
      const IProduct& target_product,
      const Symbol* target_symbol,
      const LmProductConfig& lm_product_config,
      ProductEncyclopedia* product_cache,
      const ::presto::SymbolManager* symbol_manager,
      std::shared_ptr<::fastfeature::KlineInfoProto> kline_info = nullptr)
      : LinearModelLogic(mea, target_product, target_symbol, product_cache),
        enable_telemetry_(lm_product_config.enable_telemetry()),
        mea_(mea),
        lm_product_config_(lm_product_config),
        sim_account_id_(lm_product_config.sim_account_id()),
        product_cache_(product_cache),
        pred_interval_(lm_product_config.focus_config().prediction_window_sec() * 1e9),
        asizer_(lm_product_config.focus_config()),
        symbol_manager_(symbol_manager) {
    kline_info_ = kline_info;
  }

  void InitImpl(
      ::coin2::app::Driver* driver,
      coin2::exchange::base::strategy_util::StrategyLogger* strat_logger) override {
    strat_logger_ = strat_logger;
    auto curr_time = timestamp_override_;
    symbol_manager_->GetAllSymbols(&symbols_);
    asizer_.Init(curr_time, mea_, symbols_);
    coin2::exchange::symbology::ProductRequest req;
    req.set_mea(mea_.String());
    for (auto& [name, oe] : driver->strategy()->order_executors(sim_account_id_)) {
      (void)name;
      if (oe->mea() == mea_) {
        oe_ = oe.get();
        if (enable_telemetry_) {
          LOG(INFO) << "Set Latency Recorder";
          oe_->order_system()->gateway()->set_latency_recorder(
              strat_logger->mutable_latency_recorder());
        }
        for (auto& [prod, prod_conf] : oe->config().products()) {
          req.set_norm(prod);
          for (auto&& product:
              ResolveProductRequestFromPotentiallyWildcard(req, curr_time, false)) {
            products_.push_back(std::move(product));
            if (sim_account_id_ == prod_conf.sim_account_id()) {
              const auto& product = products_.back();
              const Symbol* symbol = product_strategy_->ResolveSymbol(mea_, product->relative_norm());
              if (!symbol) {
                continue;
              }
              if (strats_.count(symbol) == 0) {
                strats_[symbol].reset(new LmSubStrategy(mea_, product.get(), symbol, lm_product_config_, curr_time, product_cache_));
                last_logged_tss_[symbol] = 0L;
              }
              CHECK_EQ(strats_.count(symbol), 1) << symbol->symbol_string();
              prodstrats_[GetProductKey(product.get())] = strats_.at(symbol).get();
              configs_.resize(configs_.size() + 1);
              auto& config = configs_.back();
              config.Clear();
              config.set_symbol(product->absolute_norm());
              config.set_lot_size(prod_conf.qty().max_lot_size());
              config.set_min_pos(prod_conf.qty().min_position());
              config.set_max_pos(prod_conf.qty().max_position());
            }
          }
        }
      }
    }
    for (auto& [name, oe] : driver->strategy()->order_executors(sim_account_id_)) {
      (void)name;
      if (oe->mea() == mea_) {
        lholder_.InitExecutor(oe.get(), strat_logger, enable_telemetry_, curr_time, true);
        for (auto&& [symbol, substrat] : strats_) {
          CHECK(lholder_.acc_req.has_value());
          substrat->lholder_.InitAccReq(lholder_.acc_req.value());
          substrat->lholder_.InitExecutor(oe.get(), strat_logger, false, curr_time, true);
        }
      }
    }
    AddKlineInfo();
    WarmupVariable(strat_logger);
  }

  void WarmupVariable(coin2::exchange::base::strategy_util::StrategyLogger* strat_logger) {
    auto curr_time = GetCurrentTimestamp();
    for (auto&& [symbol, substrat] : strats_) {
      substrat->WarmupVariableFromStratLog(strat_logger, curr_time);
    }
  }

  void AddKlineInfo() {
    LOG(ERROR) << "AddKlineInfo";
    if (kline_info_ == nullptr) {
      LOG(ERROR) << "kline_info_ is nullptr";
      return;
    }
    for (const auto& kline : kline_info_->klines()) {
      std::string symbol_str = kline.symbol();
      LOG(ERROR) << StringPrintf("add kline info of symbol %s",
          symbol_str.c_str());
      const Symbol* symbol =
        product_strategy_->converter->symbol_manager->FromSymbolString(symbol_str);
      if (!strats_.count(symbol)) continue;
      for (const auto& ohlc : kline.ohlc()) {
        if (kline.interval_ns() != 60'000'000'000L) {
          continue; 
        }
        double midp = (ohlc.open() + ohlc.close()) * 0.5;
        strats_[symbol]->UpdatePrice(ohlc.kline_timestamp(), ohlc.close());
        strats_[symbol]->UpdateLiquidity(ohlc.kline_timestamp(),
            midp * ohlc.volume());
      }
    }
    kline_info_.reset();
  }

  void OnFill(
      int64_t timestamp,
      coin::proto::TradeSide trade_side,
      double fill_price,
      double fill_qty) final {
  }

  void onAccountInfo(const OrderUpdate& upd) final {
    if (!oe_) return;
    if (!upd.is_account_info()) return;
    if (upd.sim_account_id && sim_account_id_ != *upd.sim_account_id) return;
    for (auto&& [symbol, substrat] : strats_) {
      (void)symbol;
      substrat->onAccountInfo(upd, oe_);
    }
  }

  void onAccountOrder(const OrderUpdate& upd) final {
    if (!oe_) return;
    if (upd.sim_account_id && sim_account_id_ != *upd.sim_account_id) return;
    auto pkey = GetProductKey(&upd.product());
    auto it = prodstrats_.find(pkey);
    if (it != prodstrats_.end() && it->second) {
      it->second->onAccountOrder(upd, oe_);
    }
  }

  void UpdateMidp(
      StrategyReporter2* strat_reporter,
      const IProduct* target_product_unused) final {
    for (auto&& [symbol, substrat] : strats_) {
      (void)symbol;
      substrat->UpdateMidp(strat_reporter, &asizer_);
    }
  }

  void onFeed(
      const Symbol* symbol,
      const FeedUpdate& upd,
      const ::fastfeature::FeedMessage& feed_msg,
      LinearModel* linear_model,
      int64_t* lm_feed_id,
      const int64_t* global_feed_id,
      LinearModelDelegateProto* delegate,
      bool simulation) override;

  void UpdateCurrTime(const Symbol* symbol);

 private:
  std::vector<std::unique_ptr<IProduct>> products_;
  const bool enable_telemetry_;
  const MarketExchangeApi mea_;
  LmProductConfig lm_product_config_;
  int64_t sim_account_id_;
  int64_t first_feed_time_ = 0L;
  ProductEncyclopedia* product_cache_ = nullptr;
  OrderExecutorSystem* oe_ = nullptr;
  std::unordered_map<const Symbol*, std::unique_ptr<LmSubStrategy>> strats_;
  std::unordered_map<std::string, LmSubStrategy*> prodstrats_;
  std::vector<coin::proto::coin_executor::AggressiveExecutorConfig> configs_;
  PredictionInterval pred_interval_;
  AggregatedSizer asizer_;
  const ::presto::SymbolManager* symbol_manager_;
  std::vector<const Symbol*> symbols_;
  std::shared_ptr<::fastfeature::KlineInfoProto> kline_info_;
  std::unordered_map<const Symbol*, int64_t> last_logged_tss_;

  // focus does not use logic holder.
  coin2::exchange::base::strategy_util::StrategyLogger* strat_logger_;

  double curr_shared_stamp = 0.0;
};

}  // namespace coin2::strategy::linear_model
